package com.qzsoft.zhjd.laboratory.modules.asphaltCombustionFurnace.entity;

import com.qzsoft.zhjd.laboratory.modules.common.ByteUtils;
import lombok.Getter;
import lombok.Setter;

import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

/**
 * 燃烧法沥青含量测定仪（燃烧炉） 数据值 解析！
 * <p>
 * 适用厂商设备型号：北京大地龙华科技发展有限公司 燃烧法沥青含量测定仪（燃烧炉） 型号：HYRS-6 出厂编号: 201058 出厂日期:2020年12月
 *
 * @author MacUncle
 * @since 2025/11/07
 */
@Setter
@Getter
public class DDLHBurnerProtocolParser {

    // 常量 请求响应头 固定！
    public static final byte HDR1_HEX = (byte) 0xEB; // 固定请求/应答头[1B]
    public static final byte HDR2_HEX = (byte) 0x90; // 固定请求/应答头[2B]

    public static final byte REALTIME_HEX = 0x01; // 命令字段 实时数据[0x01]
    public static final byte RESULT_HEX = 0x09; // 命令字段 实验结果[0x09]

    public static final byte[] READ_REQUEST_HEX = new byte[]{HDR1_HEX, HDR2_HEX, 0x00, 0x01, 0x00, 0x2C, (byte) 0x87}; // 读取数据请求
    public static final byte[] ACK_HEX = new byte[]{HDR1_HEX, HDR2_HEX, 0x00, 0x01, (byte) 0xAA, (byte) 0xAC, (byte) 0xF8}; // 确认收到相应

    /**
     * 地址
     */
    private byte address;

    /**
     * 命令字段
     *
     * @see #REALTIME_HEX
     * @see #RESULT_HEX
     */
    private byte command;

    /**
     * 原始数据数组
     */
    private byte[] rawData;

    /**
     * 实时数据
     * <p>
     * 当 command == {@link #REALTIME_HEX}
     */
    private RealtimeData realtimeData;

    /**
     * 实验结果！
     * <p>
     * 当 command == {@link #RESULT_HEX}
     */
    private TestResult testResult;

    /**
     * 是否为实验结果！
     */
    public boolean isResult() {
        return (this.command == RESULT_HEX);
    }

    /**
     * 帧解析入口
     * <p>
     * 执行流程：
     * <p>
     * A、后台下发命令：EB 90 00 01 00 2C 87
     * 设备返回帧为两种：
     * 1、在没有试验结果产生时，设备应答实时数据（命令字：0x01）
     * 2、当一次试验结束时，设备应答试验结果（命令字：0x09）
     * <p>
     * B、后台收到试验结果后，下发命令变为 EB 90 00 01 AA AC F8 作用：通知设备后台已正确接收试验结果，
     * 设备收到此命令后，恢复为实时数据发送，后台下发命令也恢复为 EB 90 00 01 00 2C 87
     */
    public static DDLHBurnerProtocolParser parseFrame(byte[] origin) throws IllegalArgumentException {
        // 最少: 帧头(2) + 地址(1) + 命令(1) + CRC(2)
        if (origin == null || origin.length < 2 + 1 + 1 + 2) {
            throw new IllegalArgumentException("帧长度太短");
        }

        // TODO：正确的帧格式大小：帧头(2) | 地址字节(1) | 命令字(1) | 数据内容(38) | CRC(2)

        // 帧头验证！
        if (origin[0] != HDR1_HEX || origin[1] != HDR2_HEX) {
            throw new IllegalArgumentException(String.format("错误的帧头: %02X %02X！", origin[0], origin[1]));
        }

        // CRC 校验：参与校验的数据为 帧头 + 地址 + 命令 + 数据
        // 去掉最后两个 CRC 字节
        if (!ByteUtils.checkModBusCRC16(origin, 0, origin.length, 2)) {
            throw new IllegalArgumentException("CRC 校验失败！");
        } else {
            System.out.println("CRC 验证通过！");
        }

        byte address = origin[2]; // 基本固定值：00全部
        byte cmd = origin[3]; // 命令字：01、09

        DDLHBurnerProtocolParser result = new DDLHBurnerProtocolParser();
        result.address = address;
        result.command = cmd;

        // 根据命令分发解析
        switch (cmd) {
            case REALTIME_HEX: // 实时数据
                // data bytes = total - header(2) - addr(1) - cmd(1) - crc(2)
                int dataLen_a = origin.length - 2 - 1 - 1 - 2; // 数据长度
                byte[] data_a = new byte[dataLen_a];
                if (dataLen_a > 0) {
                    System.arraycopy(origin, 4, data_a, 0, dataLen_a);
                }
                result.rawData = data_a;
                result.realtimeData = RealtimeData.parse(data_a);
                break;
            case RESULT_HEX: // 试验结果
                // data bytes = total - header(2) - addr(1) - cmd(1) - crc(2) - index(1)
                int dataLen_b = origin.length - 2 - 1 - 1 - 2 - 1; // 数据长度
                byte[] data_b = new byte[dataLen_b];
                if (dataLen_b > 0) {
                    System.arraycopy(origin, 4 + 1, data_b, 0, dataLen_b);
                }
                result.rawData = data_b;
                result.testResult = TestResult.parse(data_b);
                break;
            default:
                // 未知命令，仅返回 rawData
                throw new IllegalArgumentException("未知的数据类型: " + cmd);
        }

        return result;
    }

    /**
     * 返回的实时数据
     *
     * @author MacUncle
     */
    @Setter
    @Getter
    public static class RealtimeData implements Serializable {

        /**
         * 原始重量（单位：g），保留一位小数
         */
        private double originalWeight;

        /**
         * 损失重量（单位：g），保留一位小数
         */
        private double lossWeight;

        /**
         * 主温（单位：℃），保留一位小数
         */
        private double mainTemp;

        /**
         * 副温（单位：℃），保留一位小数
         */
        private double subTemp;

        /**
         * 忽略的 剩余 26 字节数据内容！
         */
        private byte[] rawExtra;

        @Override
        public String toString() {
            return String.format("原始重量: %.1fg\n", originalWeight) +
                   String.format("损失重量: %.1fg\n", lossWeight) +
                   String.format("主温: %.1f℃\n", mainTemp) +
                   String.format("副温: %.1f℃\n", subTemp);
        }

        /**
         * 解析 实时数据 (cmd = 0x01)
         *
         * <pre>
         *      | 字段   | 类型              | 长度 | 字节序要求       |
         *      | ---- | ---------------    | -- | ----------- |
         *      | 原始重量 | long (4B)       | 4  | **大端**      |
         *      | 损失重量 | long (4B)       | 4  | **大端**      |
         *      | 主温   | short (2B)      | 2  | **大端**      |
         *      | 副温   | short (2B)      | 2  | **大端**      |
         *      | 无效的内容（忽略）   | short (26B)      | 2  | **任意填充**      |
         * </pre>
         *
         * @param data 待解析的数据数组
         * @return {@link RealtimeData }
         * @throws IllegalArgumentException 数据有误！
         */
        public static RealtimeData parse(byte[] data) throws IllegalArgumentException {
            if (data.length < 38) {
                throw new IllegalArgumentException("实时数据长度应为38字节！");
            }

            RealtimeData r = new RealtimeData();
            int pos = 0;

            // 取前4字节为重量（big-endian）
            int rawWeight = ByteUtils.readIntBE(data, pos);
            pos += 4;
            r.originalWeight = rawWeight / 10.0;

            int rawLoss = ByteUtils.readIntBE(data, pos);
            pos += 4;
            r.lossWeight = rawLoss / 10.0;

            int mainTempRaw = ByteUtils.readShortBE(data, pos);
            pos += 2;
            r.mainTemp = mainTempRaw / 10.0; // 保留1位小数

            int subTempRaw = ByteUtils.readShortBE(data, pos);
            pos += 2;
            r.subTemp = subTempRaw / 10.0;

            // 其余 26 字节忽略 暂时保留即可！
            r.rawExtra = Arrays.copyOfRange(data, pos, data.length);
            pos += 26;

            return r;
        }

    }

    /**
     * 返回的实验结果
     *
     * @author MacUncle
     */
    @Setter
    @Getter
    public static class TestResult implements Serializable {

        /**
         * 试验编号
         */
        private String testId;

        /**
         * 验类型
         */
        private String testType;

        /**
         * 原始重量（单位：g），保留一位小数
         */
        private double originalWeight;

        /**
         * 损失重量（单位：g），保留一位小数
         */
        private double lossWeight;

        /**
         * 补偿系数（百分比数值），保留两位小数
         * <p>
         * 如：01 2C[300] 表示 3.00%
         */
        private double compensateFactor;

        /**
         * 沥青含量（百分比数值），保留两位小数
         * <p>
         * 如：01 2C[300] 表示 3.00%
         */
        private double asphaltContent;

        /**
         * 油石比（百分比数值），保留两位小数
         * <p>
         * 如：01 2C[300] 表示 3.00%
         */
        private double oilStoneRatio;

        /**
         * 主温（单位：℃） 整数值
         */
        private int mainTemp;

        /**
         * 补偿重量（单位：g），保留一位小数
         */
        private double compensateWeight;

        /**
         * 状态字段（设备控制器使用，协议解析时不用考虑）
         * <p>
         * 0xAA表示存储有效，0xFF表示该记录已注销
         */
        @Deprecated
        private byte status;

        /**
         * 实验时长-小时
         */
        private int hour;

        /**
         * 实验时长-分钟
         */
        private int minute;

        /**
         * 实验时长-秒
         */
        private int second;

        public int getMinute() {
            if (hour == 0) {
                return minute;
            }
            return hour * 60 + minute;
        }

        @Override
        public String toString() {
            return "试验编号: " + testId + "\n" +
                   "试验类型: " + testType + "\n" +
                   String.format("原始重量: %.1fg\n", originalWeight) +
                   String.format("损失重量: %.1fg\n", lossWeight) +
                   String.format("补偿系数: %.2f%%\n", compensateFactor) +
                   String.format("沥青含量: %.2f%%\n", asphaltContent) +
                   String.format("油石比: %.2f%%\n", oilStoneRatio) +
                   String.format("主温: %d℃\n", mainTemp) +
                   String.format("补偿重量: %.1fg\n", compensateWeight) +
                   String.format("状态(无需关心): 0x%02X (%s)\n", status, (status == (byte) 0xAA ? "存储有效" : "记录注销")) +
                   String.format("时间: %02d:%02d:%02d\n", hour, minute, second);
        }

        /**
         * 解析 实验结果(cmd = 0x01)
         *
         * <pre>
         * | 字段   | 类型              | 长度 | 字节序要求       |
         * | ---- | ---------------    | -- | ----------- |
         * | 试验编号 | BYTE[8]         | 8  | 无需调整（ASCII） |
         * | 试验类型 | BYTE[8]         | 8  | 无需调整（ASCII） |
         * | 原始重量 | long (4B)       | 4  | **小端**      |
         * | 损失重量 | long (4B)       | 4  | **小端**      |
         * | 补偿系数 | short (2B)      | 2  | **大端**      |
         * | 沥青含量 | short (2B)      | 2  | **大端**      |
         * | 油石比  | short (2B)      | 2  | **大端**      |
         * | 主温   | short (2B)      | 2  | **大端**      |
         * | 补偿重量 | short (2B, 有符号) | 2  | **大端**      |
         * | 状态   | byte            | 1  | 无需调整        |
         * | 时间   | byte[3]         | 3  | 无需调整        |
         * </pre>
         *
         * @param data 数据数组
         * @return {@link TestResult }
         * @throws IllegalArgumentException
         */
        public static TestResult parse(byte[] data) throws IllegalArgumentException {
            if (data.length < 38) {
                throw new IllegalArgumentException("实时数据长度应为38字节！");
            }

            TestResult r = new TestResult();
            int pos = 0;

            // 1. 试验编号 (8 ASCII)
            r.testId = new String(data, pos, 8, StandardCharsets.US_ASCII).trim();
            pos += 8;

            // 2. 试验类型 (8 ASCII)
            r.testType = new String(data, pos, 8, StandardCharsets.US_ASCII).trim();
            pos += 8;

            // 3. 原始重量 (long 小端)
            r.originalWeight = ByteUtils.readIntLE(data, pos) / 10.0;
            pos += 4;

            // 4. 损失重量 (long 小端)
            r.lossWeight = ByteUtils.readIntLE(data, pos) / 10.0;
            pos += 4;

            // 5. 补偿系数 (short 大端)
            r.compensateFactor = ByteUtils.readShortBE(data, pos) / 100.0;
            pos += 2;

            // 6. 沥青含量 (short 大端)
            r.asphaltContent = ByteUtils.readShortBE(data, pos) / 100.0;
            pos += 2;

            // 7. 油石比 (short 大端)
            r.oilStoneRatio = ByteUtils.readShortBE(data, pos) / 100.0;
            pos += 2;

            // 8. 主温 (short 大端)
            r.mainTemp = ByteUtils.readShortBE(data, pos);
            pos += 2;

            // 9. 补偿重量 (short 大端，带符号)
            r.compensateWeight = ByteUtils.readShortBE(data, pos) / 10.0;
            pos += 2;

            // 10. 状态字节
            r.status = data[pos++];

            // 11. 时间 (3 字节，时、分、秒)
            r.hour = data[pos++] & 0xFF;
            r.minute = data[pos++] & 0xFF;
            r.second = data[pos++] & 0xFF;

            return r;
        }

    }
}
